<script setup name="AsFilePreview">
  /*
   <AsFilePreview ref="asFilePreviewRef" :urls="urls" />
     const urls ='https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg,http://static.shanhuxueyuan.com/test.pdf,http://static.shanhuxueyuan.com/test6.docx,http://static.shanhuxueyuan.com/demo/excel.xlsx,https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa6'

   const urls = [
     'https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
     'http://static.shanhuxueyuan.com/test.pdf',
     'http://static.shanhuxueyuan.com/test6.docx',
     'http://static.shanhuxueyuan.com/demo/excel.xlsx',
   ]
  */
  import VueOfficeDocx from '@vue-office/docx'
  import VueOfficeExcel from '@vue-office/excel'
  import VueOfficePdf from '@vue-office/pdf'
  import '@vue-office/excel/lib/index.css'
  const baseApi = import.meta.env.VITE_APP_BASE_API
  const props = defineProps({
    urls: {
      type: [String, Array],
      required: true,
      default: '',
    },
    urlIndex: {
      type: Number,
      default: 0,
    },
    maxScale: {
      type: Number,
      default: 4,
    },
  })
  const showPreview = ref(false)
  const isJointProxy = (url) => {
    const result = /^(http|https):\/\//.test(url)
    return result ? url : baseApi + url
  }

  const urlsList = computed(() => {
    if (Array.isArray(props.urls)) {
      return props.urls?.map((item) => isJointProxy(item))
    } else {
      return props.urls?.split(',')?.map((item) => isJointProxy(item))
    }
  })
  const getFileName = (url) => {
    const fileName = url?.split('/')?.pop()
    return fileName?.replace(/_.*?\./, '.') || url
  }
  const imgEl = h('img', { class: 'preview-img' })
  const fileTypeMap = {
    docx: VueOfficeDocx,
    xlsx: VueOfficeExcel,
    xls: VueOfficeExcel,
    pdf: VueOfficePdf,
    png: imgEl,
    apng: imgEl,
    bmp: imgEl,
    jpg: imgEl,
    jpe: imgEl,
    jpeg: imgEl,
    gif: imgEl,
    svg: imgEl,
    tif: imgEl,
    tiff: imgEl,
    webp: imgEl,
    heif: imgEl,
    heic: imgEl,
    mp4: h('video', { class: 'preview-video', controls: 'controls' }),
    webm: h('video', { class: 'preview-video', controls: 'controls' }),
    mov: h('video', { class: 'preview-video', controls: 'controls' }),
    unknown: h('div', { class: 'nonsupport-file', innerHTML: 'Unsupported file type' }), // 不支持预览的文件类型
  }

  const uIndex = ref(props.urlIndex)
  // 时刻以外部传入的urlIndex为准
  watch(
    () => props.urlIndex,
    (newValue) => {
      uIndex.value = newValue
    },
    {
      deep: true,
    }
  )

  // 是否禁用切换按钮
  const cutLeftIsDisabled = computed(() => !urlsList.value.length || !uIndex.value)
  const cutRightIsDisabled = computed(() => !(urlsList.value.length - 1 - uIndex.value))

  // 文件类型
  const fileType = computed(() => urlsList.value[uIndex.value]?.split('.')?.pop())

  // 展示的url
  const url = computed(() => urlsList.value[uIndex.value])
  // 切换文件
  const uIndexChange = (num) => {
    if (Math.sign(num) > 0 && uIndex.value == urlsList.value.length - 1) return
    if (Math.sign(num) < 0 && uIndex.value == 0) return
    uIndex.value += num
    resetFileShow()
  }

  // 文件渲染完成
  const rendered = () => {
    resetFileTranslate()
  }

  // 旋转文件
  const fileRotate = ref(0)
  const rotateDeg = computed(() => fileRotate.value + 'deg')
  const fileRotateChange = (num) => {
    fileRotate.value = (fileRotate.value + num) % 360
    fileRotate.value < 0 && (fileRotate.value += 360)
  }
  // 缩放文件
  const fileScale = ref(1)
  const fileScaleNum = computed(() => fileScale.value.toFixed(1))
  const fileScaleChange = (num) => {
    // 重置缩放/平移
    if (num == 1) {
      fileScale.value = 1
      resetFileTranslate()
      return
    }
    if (Math.sign(num) < 0 && fileScale.value.toFixed(1) <= 0.1) return
    if (Math.sign(num) > 0 && fileScale.value.toFixed(1) >= props.maxScale) return
    fileScale.value += num
  }

  // 节流函数
  const asThrottle = (func, wait) => {
    let timeout
    return function () {
      let context = this
      let args = arguments
      if (!timeout) {
        timeout = setTimeout(function () {
          func.apply(context, args)
          timeout = null
        }, wait)
      }
    }
  }

  // 平移文件
  const offsetX = ref(0)
  const offsetY = ref(0)
  const fileTranslate = ref(['-50%', '-50%']) // 初始化居中'-50%','-50%'切换前重置-50%,-50%; 拖拽后计算居中位置
  // 重置文件显示
  const resetFileShow = () => {
    offsetX.value = 0
    offsetY.value = 0
    fileTranslate.value = ['-50%', '-50%']
    fileScale.value = 1
    fileRotate.value = 0
  }
  // 重置平移居中
  const resetFileTranslate = () => {
    const filePreviewEl = document.querySelector('.file-preview')
    const fW = -filePreviewEl.clientWidth / 2
    const fH = -filePreviewEl.clientHeight / 2
    offsetX.value = fW
    offsetY.value = fH
    fileTranslate.value = [fW, fH]
  }
  // 文件平移转换
  const fileTranslateNum = computed(() => {
    const [x, y] = fileTranslate.value
    return typeof x === 'number' && typeof y === 'number' ? `${x}px, ${y}px` : `${x}, ${y}`
  })

  const fileTranslateChange = asThrottle((clientX, clientY) => {
    const x = clientX - offsetX.value + fileTranslate.value[0]
    const y = clientY - offsetY.value + fileTranslate.value[1]
    fileTranslate.value = [x, y]
    offsetX.value = clientX
    offsetY.value = clientY
  }, 16)

  const isDragging = ref(false)
  const previewMousedownChange = (e) => {
    isDragging.value = true
    if (!e.ctrlKey) return
    e.preventDefault()
    e.target.style.cursor = 'grabbing'
    offsetX.value = e.clientX
    offsetY.value = e.clientY
  }
  const previewMousemoveChange = asThrottle((e) => {
    if (!e.ctrlKey) return
    if (!isDragging.value) return
    fileTranslateChange(e.clientX, e.clientY)
  }, 16)
  const previewMouseupChange = (e) => {
    isDragging.value = false
    if (!e.ctrlKey) return
    fileTranslateChange(e.clientX, e.clientY)
    e.target.style.cursor = 'default'
  }

  const close = () => {
    showPreview.value = false
    resetFileShow()
  }
  const open = () => {
    showPreview.value = true
  }

  onMounted(() => window.addEventListener('resize', resetFileShow))
  onUnmounted(() => window.removeEventListener('resize', resetFileShow))

  defineExpose({ open, close })
</script>

<template>
  <Teleport to="body">
    <div class="asFilePreview" v-if="showPreview">
      <div class="file-name">{{ getFileName(url) }}</div>
      <el-icon class="close-btn circle-btn" @click="close"><Close /></el-icon>
      <div class="file-preview-box">
        <component
          class="file-preview"
          :src="url"
          :is="fileTypeMap[fileType] || fileTypeMap.unknown"
          @load="rendered"
          @rendered="rendered"
          @mousedown.stop="previewMousedownChange"
          @mousemove.stop="previewMousemoveChange"
          @mouseup.stop="previewMouseupChange"
        />
      </div>
      <div class="cut-btn">
        <el-icon @click="uIndexChange(-1)" :class="['circle-btn', 'left', { disabled: cutLeftIsDisabled }]"><ArrowLeft /></el-icon>
        <el-icon @click="uIndexChange(1)" :class="['circle-btn', 'right', { disabled: cutRightIsDisabled }]"><ArrowRight /></el-icon>
      </div>
      <div class="file-index">{{ uIndex + 1 }} / {{ urlsList.length }}</div>
      <div class="option-btn">
        <el-icon class="circle-btn" @click="fileScaleChange(-0.1)"><ZoomOut /></el-icon>
        <el-icon class="circle-btn" @click="fileScaleChange(0.1)"><ZoomIn /></el-icon>
        <el-icon class="circle-btn" @click="fileScaleChange(1)"><Aim /></el-icon>
        <el-icon class="circle-btn" @click="fileRotateChange(-90)"><RefreshLeft /></el-icon>
        <el-icon class="circle-btn" @click="fileRotateChange(90)"><RefreshRight /></el-icon>
      </div>
    </div>
  </Teleport>
</template>

<style lang="scss" scoped>
  .asFilePreview {
    overflow: hidden;
    $bg-05: rgb(0, 0, 0, 0.5);
    $btnZIndex: 9999;
    $viewZIndex: 9998;
    position: fixed;
    inset: 0;
    background: $bg-05;
    z-index: $viewZIndex;
    .file-name {
      position: absolute;
      top: 10px;
      left: 10px;
      z-index: $btnZIndex;
      color: #fff;
      font-size: 20px;
      font-weight: bold;
      text-shadow: 2px 2px 4px $bg-05;
    }
    .close-btn {
      position: absolute;
      top: 10px;
      right: 10px;
      z-index: $btnZIndex;
    }
    .circle-btn {
      border-radius: 50%;
      background: $bg-05;
      width: 40px;
      height: 40px;
      font-size: 26px;
      line-height: 40px;
      color: #fff;
      cursor: pointer;
      box-shadow: 2px 2px 4px $bg-05;
      pointer-events: all;
    }
    .cut-btn {
      position: absolute;
      top: 50%;
      transform: translateY(-50%);
      width: 100%;
      display: flex;
      justify-content: space-between;
      pointer-events: none;

      .left {
        left: 10px;
        z-index: $btnZIndex;
      }
      .right {
        z-index: $btnZIndex;
        right: 10px;
      }
    }
    .circle-btn.disabled {
      opacity: 0.5;
    }
    .file-index {
      position: absolute;
      z-index: $btnZIndex;
      bottom: 48px;
      left: 50%;
      transform: translateX(-50%);
      user-select: none;
      color: #fff;
      pointer-events: none;
      text-shadow: 2px 2px 4px $bg-05;
    }
    .option-btn {
      padding: 2px 20px;
      border-radius: 50px;
      background: $bg-05;
      position: absolute;
      z-index: $btnZIndex;
      bottom: 4px;
      left: 50%;
      transform: translateX(-50%);
      display: flex;
      gap: 10px;
      box-shadow: 2px 2px 4px $bg-05;
      > * {
        background: transparent;
        box-shadow: none;
      }
    }
    .file-preview-box {
      width: calc(100% - 120px);
      height: 86vh;
      position: relative;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      text-align: center;
      overflow: hidden;
      :deep(.file-preview) {
        opacity: 1;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(v-bind(fileTranslateNum)) scale(v-bind(fileScaleNum)) rotate(v-bind(rotateDeg));
        overflow: auto;
        animation: 0.6s onCut ease-in-out;
      }
      @keyframes onCut {
        from {
          opacity: -6;
          top: 0;
          left: 0;
          transform: translate(0) scale(-6) rotate(v-bind(rotateDeg));
        }
      }

      :deep(.preview-img) {
        user-select: none;
      }
      .vue-office-excel,
      .vue-office-docx,
      .vue-office-pdf {
        height: 86vh;
        width: 100%;
        scrollbar-width: none;
        -ms-overflow-style: none;
      }
      :deep(.docx-wrapper) {
        background: transparent;
      }
      :deep(.vue-office-pdf-wrapper) {
        background: transparent !important;
      }
      :deep(.nonsupport-file) {
        color: #fff;
        font-size: 30px;
        text-shadow: 2px 2px 4px $bg-05;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        animation: none;
      }
    }
  }
</style>
